ECS タスクの停止理由を Slack へ通知してみた

ECS タスクの停止理由を Slack へ通知してみた

ECS のタスク停止理由を Slack に通知する季節が来ました。カスタム通知機能を利用して、本当に見たい内容を通知してみました。
Clock Icon2024.07.31

こんにちは! AWS 事業本部コンサルティング部のたかくに(@takakuni_)です。

気が付けば、夏ですね。 ECS のタスク停止理由を Slack に通知する季節が来ました。

無理矢理感すごいですが、 ECS のタスク停止理由を Slack に通知してみたいと思います。

前置き

そもそもなぜ ECS タスクの停止理由を拾いたいのかというところから。

停止した ECS タスクは「最低でも 1 時間表示される」と記載がありますが、逆を言えば、後から確認できる仕組みを設けないと 1 時間後には消える可能性があります。

Currently, stopped tasks appear in the returned results for at least one hour.

DescribeTasks - Amazon Elastic Container Service

そこで、多くの ECS ユーザーは EventBridge 経由でタスクの停止理由を記録するような仕組みを設けます。

https://dev.classmethod.jp/articles/stop-ecs-task-reason-cloudwatch-logs/

AWS からも ECS Stopped Tasks in CloudWatch Logs といったソリューションが公開されています。

Slack で停止理由を見たい

ログに記録するのは良いのですが、ログを漁るのも大変です。 Slack, Teams, メールなど何かしらの通知を設ける方が多いのではないでしょうか。(今回は Slack 通知を想定してみます。)

デフォルトの状態で ECS タスクの停止を Slack 通知してみましょう。次のような通知メッセージが飛ぶと思います。

2024-07-26 at 11.33.25-random(チャンネル) - shinnosuke.takakuni - Slack@2x

ご覧の通り、残念ながらタスクの停止理由が明記されていません。通知の時点で停止理由を見たいケースも多いのではないでしょうか。

そこで、今回は ECS のタスク停止理由を Slack に通知してみたいと思います。

通知内容をカスタマイズする

AWS Chatbot は Slack への通知をカスタマイズした内容で通知できます。カスタマイズ機能の登場以前は、 AWS Lambda で通知内容をカスタマイズすれば実装できた模様ですが、今回は Lambda 無しで実装してみたいと思います。

https://dev.classmethod.jp/articles/aws-chatbot-custom-notification/

構成図

今回は以下の構成で Slack へ通知してみたいと思います。

EventBridge でタスクの停止を拾い、SNS, Chatbot 経由で Slack へ通知するシンプルな構成です。

Untitled(86)

やってみる

SNS

通知先の SNS トピックを作成します。トピック名は ecs-stop-topic など、よしなに名前をつけます。

2024-07-31 at 15.53.58-トピックの作成  トピック  Simple Notification Service  ap-northeast-1

AWS Chatbot

AWS Chatbot のチャンネルの設定を行います。設定名も任意の名前を指定しチャンネルタイプは、事前に作成した Slack チャンネルを設定します。

2024-07-31 at 16.00.41-Slack チャネル設定を作成  AWS Chatbot  Global

とくに IAM を使って AWS CLI を実行する必要はないため、チャネルガードレールポリシーは ReadOnlyAccess で設定し、必要に応じてユーザーレベルのロールを設定します。

2024-07-31 at 16.08.45-Slack チャネル設定を作成  AWS Chatbot  Global

EventBridge

EventBridge では ECS の停止(タスクの状態変更)イベントを拾って、 SNS に通知します。

Amazon ECS task state change events - Amazon Elastic Container Service

執筆時点で、ドキュメントで記載されているタスクの停止イベントはバージョン 4 から更新が止まっていますが、実際はバージョン 6 でイベントが発生していたため、次のイベントをサンプルとしてルールを作成します。

{
	"version": "0",
	"id": "dcfeb05f-8046-d26b-0250-22353be17ed5",
	"detail-type": "ECS Task State Change",
	"source": "aws.ecs",
	"account": "123456789012",
	"time": "2021-08-11T09:15:14Z",
	"region": "ap-northeast-1",
	"resources": [
		"arn:aws:ecs:ap-northeast-1:123456789012:task/hato-ecs-cluster/866773c124284ea6bc488f7a3be8c3c2"
	],
	"detail": {
		"attachments": [
			{
				"id": "ddd8dec3-6a67-42cc-b869-6eb050a4ba8b",
				"type": "eni",
				"status": "DELETED",
				"details": [
					{
						"name": "subnetId",
						"value": "subnet-01234567890123456"
					},
					{
						"name": "networkInterfaceId",
						"value": "eni-09449af85d9145c2f"
					},
					{
						"name": "macAddress",
						"value": "00:11:22:33:44:55"
					},
					{
						"name": "privateDnsName",
						"value": "ip-10-0-0-1.ap-northeast-1.compute.internal"
					},
					{
						"name": "privateIPv4Address",
						"value": "10.0.0.1"
					}
				]
			}
		],
		"availabilityZone": "ap-northeast-1a",
		"clusterArn": "arn:aws:ecs:ap-northeast-1:123456789012:cluster/hato-ecs-cluster",
		"connectivity": "CONNECTED",
		"connectivityAt": "2021-08-11T09:01:48.973Z",
		"containers": [
			{
				"containerArn": "arn:aws:ecs:ap-northeast-1:123456789012:container/hato-ecs-cluster/866773c124284ea6bc488f7a3be8c3c2/96574b15-39b9-41fb-b2ea-2362089cd242",
				"exitCode": 0,
				"lastStatus": "STOPPED",
				"name": "nginx",
				"image": "nginx:stable",
				"runtimeId": "123456c124284ea6bc488f7a3be8c3c2-2531612879",
				"taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/hato-ecs-cluster/866773c124284ea6bc488f7a3be8c3c2",
				"networkInterfaces": [
					{
						"attachmentId": "ddd8dec3-6a67-42cc-b869-6eb050a4ba8b",
						"privateIpv4Address": "10.0.0.1"
					}
				],
				"cpu": "0"
			}
		],
		"cpu": "256",
		"createdAt": "2021-08-11T09:01:41.903Z",
		"desiredStatus": "STOPPED",
		"enableExecuteCommand": false,
		"ephemeralStorage": {
			"sizeInGiB": 20
		},
		"executionStoppedAt": "2021-08-11T09:14:51.927Z",
		"group": "service:hato-ecs-service",
		"launchType": "FARGATE",
		"lastStatus": "STOPPED",
		"memory": "512",
		"overrides": {
			"containerOverrides": [
				{
					"name": "nginx"
				}
			]
		},
		"platformVersion": "1.4.0",
		"pullStartedAt": "2021-08-11T09:01:58.639Z",
		"pullStoppedAt": "2021-08-11T09:02:05.671Z",
		"startedAt": "2021-08-11T09:02:06.509Z",
		"startedBy": "ecs-svc/4197414922177771410",
		"stoppingAt": "2021-08-11T09:14:40.449Z",
		"stoppedAt": "2021-08-11T09:15:14.753Z",
		"stoppedReason": "Task stopped by user",
		"stopCode": "UserInitiated",
		"taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/hato-ecs-cluster/866773c124284ea6bc488f7a3be8c3c2",
		"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/hato-ecs-task:9",
		"updatedAt": "2021-08-11T09:15:14.753Z",
		"version": 6
	}
}

https://dev.classmethod.jp/articles/tsnote-ecs-task-stoppedreason/

余談ですが、スタンドアロンタスクを停止した場合は以下のようなイベントが発生します。 group の部分が若干違いますね。

{
	"version": "0",
	"id": "9c8dfbd2-68ba-7f92-3e3a-ddf8fe031238",
	"detail-type": "ECS Task State Change",
	"source": "aws.ecs",
	"account": "123456789012",
	"time": "2024-07-30T07:40:39Z",
	"region": "ap-northeast-1",
	"resources": [
		"arn:aws:ecs:ap-northeast-1:123456789012:task/ecs-exec-sample-cluster/7bfd23441454472eba18019c84a9e10f"
	],
	"detail": {
		"attachments": [
			{
				"id": "58ab7197-3b55-49e1-ba4b-6457a563f06e",
				"type": "eni",
				"status": "DELETED",
				"details": [
					{
						"name": "subnetId",
						"value": "subnet-05a65d661b1a74795"
					},
					{
						"name": "networkInterfaceId",
						"value": "eni-0f1855d144560b0a0"
					},
					{
						"name": "macAddress",
						"value": "06:ec:0d:f9:d1:47"
					},
					{
						"name": "privateDnsName",
						"value": "ip-172-31-38-28.ap-northeast-1.compute.internal"
					},
					{
						"name": "privateIPv4Address",
						"value": "172.31.38.28"
					}
				]
			}
		],
		"attributes": [
			{
				"name": "ecs.cpu-architecture",
				"value": "x86_64"
			}
		],
		"availabilityZone": "ap-northeast-1a",
		"clusterArn": "arn:aws:ecs:ap-northeast-1:123456789012:cluster/ecs-exec-sample-cluster",
		"connectivity": "CONNECTED",
		"connectivityAt": "2024-07-30T07:39:27.215Z",
		"containers": [
			{
				"containerArn": "arn:aws:ecs:ap-northeast-1:123456789012:container/ecs-exec-sample-cluster/7bfd23441454472eba18019c84a9e10f/eca02b50-851c-4ea3-9bdd-8f188f78e801",
				"exitCode": 143,
				"lastStatus": "STOPPED",
				"name": "bastion",
				"image": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-public/amazonlinux/amazonlinux",
				"imageDigest": "sha256:41051251bfef3a5c572404d3bedb0d32fcec4e60a955dbfa0620c7b5d82a1f86",
				"runtimeId": "7bfd23441454472eba18019c84a9e10f-607325679",
				"taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/ecs-exec-sample-cluster/7bfd23441454472eba18019c84a9e10f",
				"networkInterfaces": [
					{
						"attachmentId": "58ab7197-3b55-49e1-ba4b-6457a563f06e",
						"privateIpv4Address": "172.31.38.28"
					}
				],
				"cpu": "0"
			}
		],
		"cpu": "256",
		"createdAt": "2024-07-30T07:39:23.419Z",
		"desiredStatus": "STOPPED",
		"enableExecuteCommand": false,
		"ephemeralStorage": {
			"sizeInGiB": 20
		},
		"executionStoppedAt": "2024-07-30T07:40:15.736Z",
		"group": "family:ecs-exec-sample-bastion-td",
		"launchType": "FARGATE",
		"lastStatus": "STOPPED",
		"memory": "512",
		"overrides": {
			"containerOverrides": [
				{
					"name": "bastion"
				}
			]
		},
		"platformVersion": "1.4.0",
		"pullStartedAt": "2024-07-30T07:39:44.948Z",
		"pullStoppedAt": "2024-07-30T07:39:49.409Z",
		"startedAt": "2024-07-30T07:39:50.139Z",
		"stoppingAt": "2024-07-30T07:40:03.397Z",
		"stoppedAt": "2024-07-30T07:40:39.867Z",
		"stoppedReason": "Task stopped by user",
		"stopCode": "UserInitiated",
		"taskArn": "arn:aws:ecs:ap-northeast-1:123456789012:task/ecs-exec-sample-cluster/7bfd23441454472eba18019c84a9e10f",
		"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:123456789012:task-definition/ecs-exec-sample-bastion-td:25",
		"updatedAt": "2024-07-30T07:40:39.867Z",
		"version": 6
	}
}

イベントパターン

イベントパターンはどのイベントが発生した時に Rule を発報するか定義します。今回は ecs-exec-sample-cluster 内の ECS タスクが停止した場合 を拾ってみます。

{
	"source": ["aws.ecs"],
	"detail-type": ["ECS Task State Change"],
	"detail": {
		"clusterArn": [
			"arn:aws:ecs:ap-northeast-1:123456789012:cluster/ecs-exec-sample-cluster"
		],
		"desiredStatus": ["STOPPED"],
		"lastStatus": ["STOPPED"]
	}
}

ECS タスクが異常終了(終了コードが 0 以外)の場合に絞りたいケースは以下をご覧ください。

https://dev.classmethod.jp/articles/stop-ecs-task-reason-cloudwatch-logs/

入力パス

入力パスはターゲット(今回だと SNS トピック)にイベントを渡す前に、イベントの内容をカスタマイズする機能です。

今回は、イベントから以下の情報を Slack に通知したいため、入力パスを編集していきます。

{
	"account": "$.account",
	"availabilityZone": "$.detail.availabilityZone",
	"clusterArn": "$.detail.clusterArn",
	"resource": "$.resources[0]",
	"stoppedAt": "$.detail.stoppedAt",
	"stopCode": "$.detail.stopCode",
	"stoppedReason": "$.detail.stoppedReason"
}

テンプレート

テンプレートは、ターゲットに送信するイベントの最終系です。

今回は AWS Chatbot のカスタム通知機能の形式に合わせたいため、以下を指定します。

{
	"version": "1.0",
	"source": "custom",
	"content": {
		"textType": "client-markdown",
		"title": ":warning: ECS のタスクが停止されました :warning:",
		"description": "概要\n・アカウントID: `<account>`\n・アベイラビリティゾーン: `<availabilityZone>`\n・対象タスク: `<resource>`\n・停止時刻: `<stoppedAt>`\n・停止コード: `<stopCode>`\n・停止理由: `<stoppedReason>`",
		"nextSteps": [
			"終了理由を確認しタスクの終了が意図したものかどうかを確認してください",
			"参考: <https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/resolve-stopped-errors.html|Amazon ECS の停止したタスクのエラーを解決する>"
		]
	}
}

https://docs.aws.amazon.com/chatbot/latest/adminguide/custom-notifs.html

https://api.slack.com/reference/surfaces/formatting#linking-urls

動作確認

対象の ECS クラスターに属する ECS タスクを停止してみます。送信したいメッセージが Slack チャンネルに飛んできていますね。

2024-07-31 at 16.57.52-ecs-stop-channel(チャンネル) - shinnosuke.takakuni - Slack@2x

まとめ

以上、「ECS のタスク停止理由を Slack に通知してみた」でした。

Slack 通知するならカスタマイズして遊び心が欲しいですね。(絵文字も認識するため、好きな絵文字を設定してみてください。)

このブログがどなたかの参考になれば幸いです。

AWS 事業本部コンサルティング部のたかくに(@takakuni_)でした!

この記事をシェアする

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.